#include "Kernel.h"
#include <Mint/Keys.h>
#include "Drivers/Console.h"
#include "Utils/String.h"
#include "Hal/x86/CPU.h"
#include "Drivers/Console_Font8x8.h"
#include "Drivers/Console_Font8x16.h"

using namespace x86;

#define CONSOLE_MEMORY(x) (void *)( fMemoryAddress + x * 2 )
#define CONSOLE_BUFFER(x) (vuint16 *)( CONSOLE_MEMORY(x) )

#define CONSOLE_MEMORY_MONO			0xB0000
#define CONSOLE_MEMORY_VGA			0xB8000
#define CONSOLE_PORT_MONO			0x3BF
#define CONSOLE_PORT_VGA			0x3DF
#define	CONSOLE_AC_WRITE			0x3C0
#define	CONSOLE_AC_READ				0x3C1
#define	CONSOLE_MISC_WRITE			0x3C2
#define	CONSOLE_SEQ_INDEX			0x3C4
#define	CONSOLE_SEQ_DATA			0x3C5
#define	CONSOLE_DAC_READ_INDEX		0x3C7
#define	CONSOLE_DAC_WRITE_INDEX		0x3C8
#define	CONSOLE_DAC_DATA			0x3C9
#define	CONSOLE_MISC_READ			0x3CC
#define	CONSOLE_GC_INDEX			0x3CE
#define	CONSOLE_GC_DATA				0x3CF

Console sConsole;

Result Console::Initialize( void )
{
	CONSOLE_TRACE( "Initializing...\n" );

	// detect video card type
	if( ( *(uint16 *)0x410 & 0x30 ) == 0x30 )
	{
		fType = TypeMono;
		fMemoryAddress = CONSOLE_MEMORY_MONO;
		fPort = CONSOLE_PORT_MONO;
	}
	else
	{
		fType = TypeColor;
		fMemoryAddress = CONSOLE_MEMORY_VGA;
		fPort = CONSOLE_PORT_VGA;
	}

	ChangeMode( Mode80x25 );
	Clear();

	CONSOLE_TRACE( "Type=%u, Address=%08x, Port=%04x, Width=%u, Height=%u\n",
        fType, fMemoryAddress, fPort, fWidth, fHeight );

	return RESULT_OK;
}


void Console::SetPosition( uint8 x, uint8 y )
{
	fPosition = y * fWidth + x;
	UpdateCursor();
}


void Console::SetBackground( Colors color )
{
	fAttributes &= 0x0F;
	fAttributes |= (uint8)color << 4;
}


void Console::SetForeground( Colors color )
{
	fAttributes &= 0xF0;
	fAttributes |= (uint8)color;
}


void Console::Clear( void )
{
#if 0
	uint32 attribs = fAttributes << 8 | fAttributes << 24;
	Memory_Set( CONSOLE_MEMORY(0), attribs, fWidth * fHeight * 2 );
#else
	vuint16 * memory = CONSOLE_BUFFER(0);

	for( uint32 i = 0; i < fLength; i++ )
		*memory++ = fAttributes << 8;
#endif

	fPosition = 0;
}


void Console::MoveUp( void )
{
#if 0
	uint32 attribs = fAttributes << 8 | fAttributes << 24;
	Memory_Copy( CONSOLE_MEMORY(0), CONSOLE_MEMORY( fWidth ), fWidth * ( fHeight - 1 ) * 2 );
	Memory_Set( CONSOLE_MEMORY( fWidth * ( fHeight - 1 ) ), attribs, fWidth * 2 );
#else
	vuint16 * dest = CONSOLE_BUFFER(0);
	vuint16 * src = CONSOLE_BUFFER( fWidth );

	for( uint32 i = fWidth; i < fLength; i++ )
		*dest++ = *src++;

	// clear the last row
	for( uint32 i = 0; i < fWidth; i++ )
		*dest++ = fAttributes << 8;
#endif

	if( fPosition > fWidth )
		fPosition -= fWidth;
	else
		fPosition = 0;
}


bool Console::__PutCharacter( char c, void * cookie )
{
    Console * console = (Console *)cookie;

    if( console == NULL )
        return false;

	console->_PutCharacter( c );
	return true;
}

void Console::Write( const char * message, ... )
{
	Args args;
	Args_Start( args, message );
	StringFormatArgs( message, args, &__PutCharacter, this );
	Args_End( args );

	UpdateCursor();
}

void Console::Write( char character )
{
	_PutCharacter( character );
	UpdateCursor();
}

void Console::_PutCharacter( char character )
{
	if( fPosition >= fLength )
		Console::MoveUp();

	switch( character )
	{
	case '\b': // backspace
		fPosition--;
		*CONSOLE_BUFFER( fPosition ) = ( fAttributes << 8 );
		return;

	case '\n': // new line
		fPosition -= fPosition % fWidth;
		fPosition += fWidth;
		return;

	case '\t': // tab
		for( int count = 4 - ( ( fPosition % fWidth ) % 4 ); count > 0; count-- )
			_PutCharacter( ' ' );
		return;

	// change color
	case 0xF0:
	case 0xF1:
	case 0xF2:
	case 0xF3:
	case 0xF4:
	case 0xF5:
	case 0xF6:
	case 0xF7:
	case 0xF8:
	case 0xF9:
		fAttributes &= 0xF8;
		fAttributes |= character & 0x07;
		return;

	case 0xFB:
		fAttributes |= BrightColorMask;
		return;
	case 0xFD:
		fAttributes &= ~BrightColorMask;
		return;

	case 0xFF:
		fAttributes = 0x07;
		return;

	case 0xB0:
	case 0xB1:
	case 0xB2:
	case 0xB3:
	case 0xB4:
	case 0xB5:
	case 0xB6:
	case 0xB7:
	case 0xB8:
	case 0xB9:
		fAttributes &= 0x8F;
		fAttributes = ( character & 0x07 ) << 4;
		return;

	case 0xBB:
		fAttributes |= BrightColorMask << 4;
		return;
	case 0xBD:
		fAttributes &= ~( BrightColorMask << 4 );
		return;
	}

	if( IsPrintable( character ) )
		*CONSOLE_BUFFER( fPosition++ ) = character | ( fAttributes << 8 );
}


void Console::UpdateCursor( void )
{
	IO_Out8( 0x3D4, 0x0F );
	IO_Out8( 0x3D5, (uint8)( fPosition ) & 0xFF );
	IO_Out8( 0x3D4, 0x0E );
	IO_Out8( 0x3D5, (uint8)( fPosition >> 8 ) & 0xFF );
	IO_Delay();
}


void Console::ChangeMode( Modes mode )
{
	static uint8 mode_80x25[] =
	{
		0x5F, 0x4F, 0x50, 0x82, 0x55, 0x81, 0xBF, 0x1F, 0x00, 0x4F, 0x0D, 0x0E, 0x00, 0x00, 0x00, 0xA0, 0x9C, 0x8E, 0x8F, 0x28, 0x1F, 0x96, 0xB9, 0xA3, 0xFF,
		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x0C, 0x00, 0x0F, 0x08, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x00, 0xFF,
		0x03, 0x00, 0x03, 0x00, 0x02,
		0x67
	};

	static uint8 mode_80x50[] =
	{
		0x5F, 0x4F, 0x50, 0x82, 0x55, 0x81, 0xBF, 0x1F, 0x00, 0x47, 0x06, 0x07, 0x00, 0x00, 0x01, 0x40, 0x9C, 0x8E, 0x8F, 0x28, 0x1F, 0x96, 0xB9, 0xA3, 0xFF,
		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x0C, 0x00, 0x0F, 0x08, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x00, 0xFF,
		0x03, 0x00, 0x03, 0x00, 0x02,
		0x67
	};

	static uint8 mode_90x60[] =
	{
		0x6B, 0x59, 0x5A, 0x82, 0x60, 0x8D, 0x0B, 0x3E, 0x00, 0x47, 0x0D, 0x0E, 0x00, 0x00, 0x07, 0x80, 0xEA, 0x0C, 0xDF, 0x2D, 0x08, 0xE8, 0x05, 0xA3, 0xFF,
		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x08, 0x00, 0x0F, 0x08, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x0F, 0xFF,
		0x03, 0x01, 0x03, 0x00, 0x02,
		0xE7
	};

	switch( mode )
	{
	default:
	case Mode80x25:
		_SetMode( &mode_80x25[0] );
		_SetFont( &font_8x16[0], 16 );
		fWidth = 80;
		fHeight = 25;
		break;

	case Mode80x50:
		_SetMode( &mode_80x50[0] );
		_SetFont( &font_8x8[0], 8 );
		fWidth = 80;
		fHeight = 50;
		break;

	case Mode90x60:
		_SetMode( &mode_90x60[0] );
		_SetFont( &font_8x8[0], 8 );
		fWidth = 90;
		fHeight = 60;
		break;
	}

	*(uint8 *)0x44A = fWidth;
	*(uint8 *)0x484 = fHeight - 1;

	fMode = mode;
	fAttributes = 0x07;
	fPosition = 0;
	fLength = fWidth * fHeight;
}

void Console::_SetMode( uint8 * data )
{
	// write registers
	IO_Out8( fPort, 17 );
	IO_Out8( fPort + 1, IO_In8( fPort + 1 ) & 0x7F );
	IO_Out8( fPort, 3 );
	IO_Out8( fPort + 1, IO_In8( fPort + 1 ) | 0x80 );

	// don't disable writes to the crtc registers
	data[ 17 ] &= 0x7F;
	data[  3 ] |= 0x80;

	for( uint8 i = 0; i < 25; i++ )
	{
		IO_Out8( fPort, i );
		IO_Out8( fPort + 1, *data++ );
	}

	// skip attributes and controller
	data += 30;

	for( uint8 i = 0; i < 5; i++ )
	{
		IO_Out8( CONSOLE_SEQ_INDEX, i );
		IO_Out8( CONSOLE_SEQ_DATA, *data++ );
	}

	IO_Out8( CONSOLE_MISC_WRITE, *data );
}


void Console::_SetFont( uint8 * data, uint8 height )
{
	static uint16 set_seq[] =
	{
		0x0100, 0x0402, 0x0704, 0x0300
	};

	static uint16 set_gc[] =
	{
		0x0204, 0x0005, 0x0C06
	};

	static uint16 reset_seq[] =
	{
		0x0100, 0x0302, 0x0304, 0x0300
	};

	static uint16 reset_gc[] =
	{
		0x0004, 0x1005, 0x0E06
	};

	IO_OutArray16( CONSOLE_SEQ_INDEX, set_seq, ARRAY_SIZE(set_seq) );
	IO_OutArray16( CONSOLE_GC_INDEX, set_gc, ARRAY_SIZE(set_gc) );

	uint8 * src = (uint8 *)data;
	uint8 * dest = (uint8 *)0xB8000;

	for( int i = 0; i < 256; i++ )
	{
		Memory_Copy( dest, src, height );
		src += height;
		dest += 32;
	}

	IO_OutArray16( CONSOLE_SEQ_INDEX, reset_seq, ARRAY_SIZE(reset_seq) );
	IO_OutArray16( CONSOLE_GC_INDEX, reset_gc, ARRAY_SIZE(reset_gc) );

	IO_Out8( fPort, 9 );
	IO_Out8( fPort + 1, IO_In8( fPort + 1 ) & 0xE0 | ( ( height - 1 ) & 0x1F ) );

	if( height == 16 )
	{
		IO_Out8( fPort, 10 );
		IO_Out8( fPort + 1, 13 );
		IO_Out8( fPort, 11 );
		IO_Out8( fPort + 1, 14 );
	}
	else
	{
		IO_Out8( fPort, 10 );
		IO_Out8( fPort + 1, 7 );
		IO_Out8( fPort, 11 );
		IO_Out8( fPort + 1, 7 );
	}
}
